WW WW WW PPPPPPPP JJ WW WW WW PP PP JJ WW WWWW WW PP PP JJ WW WW WW WW PPPPPPPP JJ WW WW WW WW PP JJ JJ WWWW WWWW PP JJ JJ WW WW PP JJJJJ ---------------------------------------------------------------- The Windows Programmer's Journal Volume 01 Copyright 1993 by Peter J. Davis Number 07 and Mike Wallace Aug 93 ---------------------------------------------------------------- A monthly forum for novice-advanced programmers to share ideas and concepts about programming in the Windows (tm) environment. Each issue is uploaded to the info systems listed below on the first of the month, but made available at the convenience of the sysops, so allow for a couple of days. You can get in touch with the editors via Internet or Bitnet at: HJ647C at GWUVM.BITNET or HJ647C at GWUVM.GWU.EDU (Pete) CompuServe: 71141,2071 (Mike) GEnie: P.DAVIS5 or you can send paper mail to: Windows Programmer's Journal 9436 Mirror Pond Dr. Fairfax, Va. 22032 We can also be reached by phone at: (703) 503-3165. The WPJ BBS can be reached at: (703) 503-3021. The WPJ BBS is currently 2400 Baud (8N1). We'll be going to 14,400 in the near future, we hope. LEGAL STUFF - Microsoft, MS-DOS, Microsoft Windows, Windows NT, Windows for Workgroups, Windows for Pen Computing, Win32, and Win32S are registered trademarks of Microsoft Corporation. - Turbo Pascal for Windows, Turbo C++ for Windows, and Borland C++ for Windows are registered trademarks of Borland International. - WordPerfect is a registered trademark of WordPerfect Corporation. - Other trademarks mentioned herein are the property of their respective owners. - WPJ is available from the WINSDK, WINADV and MSWIN32 forums on CompuServe, and the IBMPC, WINDOWS and BORLAND forums on Genie. It is also available on America Online in the Programming library. On Internet, it's available on WSMR-SIMTEL20.ARMY.MIL and FTP.CICA.INDIANA.EDU. We upload it by the 1st of each month and it is usually available by the 3rd or 4th, depending on when the sysops receive it. - The Windows Programmer's Journal takes no responsibility for the content of the text within this document. All text is the property and responsibility of the individual authors. The Windows Programmer's Journal is solely a vehicle for allowing articles to be collected and distributed in a common and easy to share form. - No part of the Windows Programmer's Journal may be re-published or duplicated in part or whole, except in the complete and unmodified form of the Windows Programmer's Journal, without the express written permission of each individual author. The Windows Programmer's Journal may not be sold for profit without the express written permission of the Publishers, Peter Davis and Michael Wallace, and only then after they have obtained permission from the individual authors. Subject Page Author(s) ---------------------------------------------------- ------ WPJ.INI ................................... 4 Pete Davis Letters ................................... 6 Readers Beginner's Column ......................... 7 Dave Campbell Window's Programmer's Guide to Serial...... 17 David Falconer Pascal in 21 Steps ........................ 22 Bill Lenson Hacker's Gash ............................. 35 Dennis Chuah ObjectWindows How-To ...................... 41 Rodney Brown Creating Help Files ....................... 43 Todd Snoddy GDI See GDI Do ............................ 48 Bernard Andrys Windows++ ................................. 57 Philip Sloss Getting in Touch with Us .................. 62 Pete & Mike Last Page ................................. 63 Mike Wallace Windows Programmer's Journal Staff: Publishers ......................... Pete Davis and Mike Wallace Editor-in-Chief .................... Pete Davis Managing Editor .................... Mike Wallace Contributing Editor ................ Dave Campbell Contributing Editor ................ Bill Lenson Contributing Editor ................ Dennis Chuah Contributing Editor ................ Mike Strock Contributing Writer ................ Rodney Brown Contributing Writer ................ Todd Snoddy Contributing Writer ................ Bernard Andrys Contributing Writer ................ David Falconer Contributing Writer ................ Philip Sloss WPJ.INI By Pete Davis Another month, another issue. We didn't do an issue last month because we didn't have enough articles early enough, but it gave us a chance to collect a lot of articles for this issue plus a few for next month's issue. That should give us a good head start. This month you'll notice a survey accompanying the magazine. We're considering putting the magazine in print. Through the help of a publishing company, this may very well happen. That would mean some major changes which I want to get out into the open right away. First of all, yes, the magazine would cost money. What would that money get you? It would get you 12 issues a year of a great magazine. Mike and I would be doing this for a living. That means that the articles would undergo serious editing, code and text would be checked and double-checked. We'd have time to make sure everything is right. At the moment, Mike and I are doing this part time and can barely afford to spend more than a few hours a month to work on the magazine. In our current form, it's almost impossible to do any real editing. Why? Well, we can't pay the authors, and it's hard enough to get articles. If we made it difficult, people wouldn't write for us. Paying the authors would fix that. We'd also be able to get some of the big names out there, like Andrew Schulman, Matt Pietrek, etc. What wouldn't change? One of our hottest areas is help for beginners. As long as I ever have anything to do with this magazine, that won't change. We will always be a place where beginners can turn for information and help. We'd also provide the more advanced articles that we've been providing over the past 6 issues. Going into print would also allow us to reach a much broader audience than we reach now. We'd be able to get to the people who can't or haven't found us online. People who subscribe prior to the first printed issue will get a discount of about 25% off the regular subscription rate of around $25. These numbers might change since we're still working out the details. We're hoping you will all go for this. In all honesty, I don't know how long the magazine would be able to survive for free. Mike and I both do a several things for a living. That - 4 - doesn't leave much time to work on the magazine and things are only getting worse. If we could make our living off the magazine, we could devote all of our time to it, which we'd both like to do. Anyway, fill out the survey and send it to my userid on CompuServe, the Internet, or via regular mail. Next month we will draw a name from all the people who responded. The winner will get a choice between: - The "Win32 Reference" manuals (a 5-book set) - A combo of the "Windows API Bible", "Windows Internals", and "Undocumented Windows" - The Windows NT Resource Kit Send the response to me at: CompuServe: 71644,3570 Internet: 71644.3570@compuserve.com or regular mail to: WPJ Survey 9436 Mirror Pond Dr. Fairfax, Va. 22032 U.S.A. We look forward to getting your responses. Peace. _Pete Davis - 5 - Letters Date: 22-Jun-93 09:10 EDT From: Scott Guthrie [100060,471] Subj: Install Program Prob with In use DLL's Mike and Pete, I just today downloaded issues 2-5 of your Journal, and I must say I'm impressed. Unfortunately, you seem to have ditched the Install program for a variety of reasons, only one of which I may know the answer to. Microsoft provides an exportable function in the USER.EXE module of Windows 3.1 called ExitWindowsExec which will exit Windows and run the executable indicated in the call and then (when the executable releases control of the processor) RESTARTS Windows. The executable it runs can include up to a 128 byte Path and file name, and an additional 128 bytes of parameters, switches, etc. The documentation I have read for this function says that it was included in Win3.1 precisely for the use of SETUP/INSTALL programs to use when they need to modify code that is currently in use by Windows (e.g., DLLs). Sorry I wasn't quicker off the mark on this, but I'm not even sure it is going to help you, as I haven't been able to get it to work precisely as advertised. Cheers, Scott Guthrie - 6 - Beginner's Column By Dave Campbell Let's see, I know I've got some code here that will show me how to put a dialog box into a DLL...no, not that file. Ok, ok, unzip that entire disk with a -v option into a file, and just search the thing. Oh, no! Guess I must have dumped it. I sure hate it when I do that, don't you? Me too. And this month, since I want to follow up on my promise of putting the file open dialog box into a DLL, I had to redo it. But that wasn't all bad, since I'm going to explain it anyway. The end result of this month's column is going to be code that runs identical to that of last month, the difference being that some of the code no longer resides in the executable. To accomplish this, we are going to pull some of the code from Hello.EXE and put it into a dynamic link library (DLL) called FileO.DLL. That's all there is to it. Now for next month... DLL short-course DLLs many times are very complex pieces of code, and by their very nature are reasonably tricky to program. That does not mean we should be afraid to make use of the power offered to us. We just need to be careful when using them, and follow the rules. Most all of us are familiar with ".LIB" files. These are very much like a collection of ".OBJ" files that we can pull out during the building of our file, and save having to write the code again. These LIBs may be locally built, and contain code personally written, or may be third-party libraries costing hundreds of dollars. In either case, the code from the LIB becomes part of your EXE file after the build is completed. If you use the LIB for three different modules, you will have pieces of that LIB file in all three of your executables. That takes up space on your hard disk, and also in memory at execution time. Execution time? Well, maybe not in DOS, but what if we had written three TSRs and used a third-party library for some of the functionality, and then loaded all three? It would be possible to have, at run-time, three exact copies of the same code in memory. Kinda wasteful, huh? I hope I'm not boring everyone, I'm simply trying to set the stage for the power of a DLL. DLL stands for "Dynamic Link Library", and as the name implies, the linking takes place - 7 - dynamically, or at run-time. In our TSR example above, if DLLs were available to us in the DOS world, only one copy of the common code would have been needed, thereby saving disk space because the EXE files would be smaller, and saving memory at run- time because the common code would only be loaded once. That's exactly what we get with DLLs in Microsoft Windows. When a call is made to a function in a DLL, Windows executes the function as if the function were local to the window running. The previous sentence is literally correct, and is one of the sticky parts of programming DLLs. DS != SS I have no intention of digging into this at the depth that is available in much of the literature, but I am going to try to give an explanation of what DS != SS means, since it is used quite often. The primary thing to remember is that DLLs do not have their own stacks. The calling application's stack gets used during execution of the DLL. This is a problem because of near/far references to data. In Windows programming, all near pointers are assumed to be DS-relative. To avoid a problem with this, either explicitly declare pointers to be FAR, or cast them that way as they are used. Hello.C First let's take a look at Hello.C. There's been quite a bit of code removed, and a little added in. I want to discuss the additions first. In the include section of Hello.C, a #include of the new "fileo.h" was added. This is necessary because some definitions that were in Hello.H are now residing in FileO.H, and our file needs information about it. Almost any DLL that you will be using from a third-party vendor will have a DLL included that you will have to link with. Normally the DLL will have the prototypes of the functions to call, and any data definitions, or messages that could be passed. Next in the global definition area, we define a HANDLE type named "hFileo". This will be used to identify the DLL after it is loaded by our program. HANDLE hFileo; Next, a prototype was added for "DoFileOpenDlg". This will be the launch point to get into the DLL to use the dialog box we - 8 - created two months ago: void DoFileOpenDlg(void); WinMain now contains the following code: hFileo = LoadLibrary("FILEO.DLL"); if (hFileo < 32) { FreeLibrary(hFileo); MessageBox(GetFocus(),"Dynamic Link Library FILEO.DLL must be present", szAppName, MB_ICONEXCLAMATION |MB_OK); return 0; } This is the code that loads (possibly) the DLL into memory. I say possibly because if Windows finds the DLL already loaded, the currently loaded copy is used. If the load is successful, LoadLibrary will return a value greater than or equal to 32. This magic number is HINSTANCE_ERROR, and any return value of this or lower is an error. On error, we report the reason to the user, and exit. Before we exit our WinMain, we must free up the DLL. We are only concerned with cleaning up after ourselves. Windows will not allow the DLL to be removed if others are using it. That's what makes the system so nice to use: FreeLibrary(hFileo); WndProc is clean until we get to "case IDM_FILEOPEN": case IDM_FILEOPEN : DoFileOpenDlg(); lstrcpy(OutMsg, szFileName); break; Instead of calling the dialog box procedure as we have done in the past, we are now calling a local procedure that will do that work for us. The code could have been inserted at this point, but for ease of reading, and comparison, it is cleaner to have it be isolated. A change has been made in the reporting of the filename in that it is transfered locally through the global variable "szFileName". That will become clear shortly: DoFileOpenDlg - 9 - This procedure is new to Hello, and as I said above, is the launcher for the DLL function. The mystery takes place in the first variable declared: FARPROC lpfnFileOpenDlgProc = GetProcAddress(hFileo,"FileOpenDlgProc"); This declares a far pointer to a function inside the DLL. GetProcAddress retrieves that pointer for us by name, given that we know the handle for the DLL. We know that because we loaded it, "hFileo". We also know the function name "FileOpenDlgProc", because we wrote that function two months ago. It just happens to be external to our program at this point. To execute that function, we do the following: (*lpfnFileOpenDlgProc)(); The file name selected will be passed back via Hello.INI, and we retrieve that into szFileName, to be used above: GetPrivateProfileString("FileO", "FileName", "NoName", szFileName, 128, "Hello.ini"); The remainder of Hello.C is identical to the previous code with the exception of the Fileo.C portions removed. Hello.DLG Hello.DLG is now split between Hello.DLG and Fileo.DLG, but nothing is added or removed, other than the split. Hello.H Hello.H is split between Hello.H and Fileo.H. Hello.DEF Hello.DEF is identical to before with the exception of the exported FileOpenDlgProc function being removed. FILEO Fileo is a simple DLL and contains the primary pieces that every DLL contains: LibMain and WEP - 10 - To simplify some of our code, and avoid any nastiness that may take away from the simplicity, static variables were defined for the filename and extension, and declared in the beginning of the Fileo code: static HANDLE hInst; static char szFileName[128]; static char szFileSpec[16] = "*.EXE"; static char szDefExt[5] = "EXE"; static WORD wFileAttr = DDL_DRIVES | DDL_DIRECTORY; LibMain The LibMain function is called by LibEntry, which is called by Windows when the DLL is loaded. The LibEntry routine is provided in the LIBENTRY.OBJ module. LibEntry initializes the DLL's heap (if a HEAPSIZE value is specified in the DLL's module- definition file) before calling the LibMain function. This simply unlocks the data segment of the library (which is locked by the LocalInit call in LIBENTRY) and returns 1. int FAR PASCAL LibMain (HANDLE hInstance, WORD wDataSeg, WORD wHeapSize, LPSTR lpszCmdLine) { if (wHeapSize > 0) UnlockData(0); hInst = hInstance; return 1; } LibMain is the place to allocate any memory necessary for use in the DLL, and to do any setup functions. WEP WEP is called by Windows when the DLL is released, and provides a place for us to do cleanup before exiting. In Windows 3.1, WEP is no longer necessary, but if we are trying to write DLLS to be globally useful, WEP should be included. int FAR PASCAL WEP (int bSystemExit) { /*--------------------------------------------------------------- get rid of things you allocated in the LibMain proc here ---------------------------------------------------------------*/ - 11 - return TRUE; } /* WEP */ In the case of Fileo, WEP has no functionality. Fileo Code The code body for Fileo consists of the two functions "lstrchr" and "FileOpenDlgProc" from the old Hello.C file. "lstrchr" has no changes at all, and was simply copied here. FileOpenDlgProc This function was stripped straight from the old Hello.C, modified slightly, and renamed "DoFileOpenDlgProc". It no longer attempts to open the file selected, the name is simply shipped back to Hello.C via an ini file, "Hello.INI" This is all accomplished through the one exported function, "FileOpenDlgProc" FileOpenDlgProc This function is small, and consists of the lines from the old Hello.C WndProc switch case: lpfnDoFileOpenDlgProc = MakeProcInstance(DoFileOpenDlgProc, hInst); DialogBox(hInst, "FILEOPEN", GetFocus(), lpfnDoFileOpenDlgProc); FreeProcInstance(lpfnDoFileOpenDlgProc); followed by the Hello.INI write of the filename: WritePrivateProfileString("FileO", "FileName", szFileName, "Hello.ini"); Fileo.DEF The DEF file will be recognizable, and has few changes from the EXE DEF file. As explained above, there is no stack, therefore no STACKSIZE. WEP must be exported, and our single exported function "FileOpenDlgProc" is there: LIBRARY FileO - 12 - DESCRIPTION 'FileOpen DLL' EXETYPE WINDOWS STUB 'WINSTUB.EXE' CODE MOVEABLE PRELOAD DATA MOVEABLE PRELOAD SINGLE HEAPSIZE 8192 EXPORTS WEP @1 RESIDENTNAME FileOpenDlgProc Fileo.mak The make file is cryptic as ever, but recognizable. The changes are few, and easy to spot. Amazingly enough, that's all there is to writing and using a very simple DLL. I've glossed over many of the nasties, and have purposefully avoided others. The bottom line is we have a DLL that's useable and easy to read. Make Files I am providing 3 make files with this code. HELLO.MAK and FILEO.MAK are the Borland make files for Hello.EXE and Fileo.DLL. MHELLO.MAK is the Microsoft make file for Hello.EXE. Try as I may, I could not produce a working make file for Microsoft for FILEO.DLL. The Hello.EXE that I produce with either make file works with the Fileo.DLL I produce from Borland, but I ran out of ideas trying to produce a make file for Microsoft for Fileo.DLL. So, the proof is left to the student. The first one to send me one that works gets mentioned here next month. ICONS Did you really think I was going to leave and not mention last month's program? Unfortunately, I was so late getting my article in that I ran the magazine late, and my "Go Suns" hit the streets after the Bulls took the third in a row. Oh well, the idea still stands. Animated icons is something we don't see a lot of in Microsoft Windows. Building all the icon images is probably the reason why. I was going to take a shot at doing a sweep second-hand clock, and got bored after 15 seconds worth of images. - 13 - The work for the animated icons starts in WinMain, where a timer is started: SetTimer(hWndMain, ID_TIMER, 500, MakeProcInstance((FARPROC)TimerProc, hInst)); Windows allows up to 16 timers to be running at any given time. Normally, SetTimer would be checked for a return value, and if the value is NULL, display a message about too many timers. The first parameter is the handle to the window whose procedure will receive the WM_TIMER messages. The second parameter is the timer ID, to identify which of 16 possibles you now have running. The third parameter is a word specifying the timer interval in milliseconds (1-65535). In our case, this is 500 msec, or 1/2 second. The fourth parameter is the instance handle of this window. InitInstance Inside InitInstance, we now are loading an Icon, and this is the "WPJ" icon that will show on the group screen, and for short periods of time when iconized: wc.hIcon = LoadIcon(hInstance, "WPJ"); /* Generic Icon */ and this value is saved in a global: hIconMain = wc.hIcon; WndProc The WM_PAINT message handler is altered to handle the icon painting: case WM_PAINT : if (IsIconic(hWnd)) { - 14 - BeginPaint(hWnd, &ps); /*--------------------------------------------------------------- Erase the background of the window with what's on the desktop. This is so the desktop bitmap will show through the transparent areas of the icon. ---------------------------------------------------------------*/ DefWindowProc(hWnd, WM_ICONERASEBKGND, (WORD)ps.hdc, 0L); /*--------------------------------------------------------------- Now draw the icon. ---------------------------------------------------------------*/ DrawIcon(ps.hdc, 0,0, hIcon); EndPaint(hWnd, &ps); } Notice that the icon drawn is passed by the generic handle "hIcon". This will allow us to change the icon image inside the Timer proc. One more thing needs to be done inside WndProc, and that is how to handle the icon when it is dragged on the screen. Fortunately, Windows sends a message while in that state, WM_QUERYDRAGICON: case WM_QUERYDRAGICON: return((LONG)(WORD)hIconMain); Consequently, when our WndProc receives that message, we return the default "WPJ" icon, for the duration of the drag. TimerProc The only function our timer proc does in this application is toggle the two bitmaps GO.ICO and SUNS.ICO: BOOL FAR PASCAL TimerProc(HWND hWnd, WORD message, WORD wParam, LONG lParam) { static BOOL which = 0; if (IsIconic(hWnd)) { if (which = !which) { hIcon = LoadIcon(hInst, "GO"); InvalidateRect(hWnd, NULL, FALSE); - 15 - SendMessage(hWndMain, WM_PAINT, NULL, NULL); } else { hIcon = LoadIcon(hInst, "SUNS"); InvalidateRect(hWnd, NULL, FALSE); SendMessage(hWndMain, WM_PAINT, NULL, NULL); } } } Depending on the entry value of the static variable "which", one of the two icons is loaded, and the entire window (the icon area) is invalidated for painting, and a message is sent to our own WndProc to execute the WM_PAINT command, which we know from above will repaint the icon. Why All of this is entertaining, sort of, but not useful. I have simply shown how a timer works, and tried to take some of the mystery out by doing something stupid. What if, instead of painting icons on a timer, we were painting windows? And, what if those windows were over the top of everything else, so that on a timer, the screen would suddenly get painted over. Why that sounds like a screen-saver! Well, kinda-sorta. We wouldn't want to do that on a hard timer, but it is close. Close enough that, given enough time, there will be the start of a series in the next issue called "Screen Savers Inside- Out". One more mystery unfurled. EndDialog That's about all there is to say about the code for this month. Unless something changes, I'm going to discuss common dialogs and show how to do all this the easy way next time. Feel free to contact me in any of the ways below. Thanks to those of you that have e-mailed me questions, keep them coming. Dave Campbell WynApse PO Box 86247 Phoenix, AZ 85080-6247 (602)863-0411 wynapse@indirect.com CIS: 72251,445 Phoenix ACM BBS (602) 970-0474 - WynApse SoftWare forum - 16 - Book Review Title: Windows Programmer's Guide to Serial Communications Author: Timothy S. Monk Publisher: SAMS Publishing By David Falconer Book Details: ISBN 0-672-30030-3 Includes Software Disk. $39.95 USA I have been searching for ages for a book which would teach me how to do Comms programming in Windows. Judging by the amount of questions in the Borland Windows forum on Compuserve, I am not alone in this quest. The book that seems to be most commonly recommended is Timothy Monk's "Windows Programmer's Guide to Serial Communications", so I bought a copy to see what it was like. Perhaps I should explain a little about me, so you have an idea of the role the book was intended to play. I'm not a novice programmer, but not that experienced either, and I'm very new to Windows programming. Having worked my way through Petzold, I decided I was ready to tackle Comms, which is what I wanted to do in the first place! Also, I learn best by trial and error, and was looking for something to get me started and that would also act as a reference later on. I have not started putting the knowledge I have obtained from this book into practice yet - priorities change - but I thought I would share what it's like as a primer. Firstly, this is not a primer for Windows programming in general. As a novice, I often found myself with Monk's book in one hand and Petzold in the other, but the Monk does provide little snippets of basic stuff that Petzold doesn't cover; connecting an application to a help file, for example. The other assumption that is made is that you have some familiarity with DOS serial Comms - at least the terms used. The book is laid out in 7 chapters and 3 appendices, over the course of which you develop a fully working Comms program, called TSMTERM, starting with the very basics and ending up with an XMODEM file transfer protocol implementation. Each chapter starts with an overview of what will be covered, and finishes with a summary of what has been achieved. There are code segments everywhere, and these are as much an integral part of the text as the writing between them is; often it is from the code that you find exactly how a particular function, or technique, is implemented. Fortunately all the code, plus fully compiled executables are provided on the accomanying disk, which - 17 - saves on a lot of typing! Each time the code is modified, advanced upon in the text, a completely new set of source code is provided on the disk. This means a hefty 3 Megabytes of memory is needed for the files, but it saves getting confused over which version of which bit of the program you should be working with at any one stage. The areas that the chapters cover are: Chapter 1: Serial Communications This chapter contains an overview of serial Comms procedures, starting with the hardware; the RS-232 link, the 3 UARTS commonly found on the PC (8250/16450 and the newer 16550) in quite a bit of detail and finally the COM ports and their adresses and interrupts. Monk then goes on to discuss the principles of DOS Comms programming, with code segments, including a look at ring buffers. The final part of the chapter discusses what is going to happen in the rest of the book, by way of a "design brief" for the TSMTERM program. Chapter 2: Windows Communications Basics. This is basically in two major sections. In the first, Monk outlines the 17 functions of the Windows Communications API, which are fully documented in Appendix A, and shows how these can be used in a typical Comms session of open port, send/receive characters, close port. These initial examples use polling of the port in the main message loop of the Windows application as the method of getting information from the port into the application. Message-based methods, which were introduced with Windows 3.1, crop up later. In the second half of the chapter, Monk creates his own API for use with the TSMTERM program, in order to simplify some of the trickier aspects of using the raw function calls. These are then all encapsulated in a DLL. This is done very matter-of-factly, but the code for the LibMain and WEP functions are provided and briefly explained. Chapter 3: Terminal Emulation Again, this chapter is in two sections. The first part covers terminal emulations, including the full specification of an ANSI terminal, which is then implemented in a DLL. There is also a word about finite state machines, on which principles the ANSI emulation is based. The second part starts assembling the application from the components created so far. The processing of the basic Windows messages is covered, as is the Windows procedure, although this is of quite a complex variety, covering menu commands as well as the more straight forward messages. Knowledge of Dialog Box methods are assumed, but I learned quite a lot about their implementation from studying the code. This is a fully fledged application, and so includes an "about" box and - 18 - interfaces to a basic help file. As well as the ANSI implementation, the program also allows the user to choose a basic TTY emulation. Chapter 4: Errors, Events, and Flow Control The good practice of calling GetCommError( ) after every read, write or port poll is introduced and TSMCOMM.DLL, created in chapter 2, is modified to include error processing. A similar process is then gone through for communications events using the GetCommEventMask( ) function and its associates. After both modifications to the DLL, the main application is also modified to give the user a choice of how to monitor errors and events. Finally there is a brief discussion of XON/XOFF and hardware flow control, after which both methods are added to TSMCOMM.DLL. Again the main application is modified to give the user a choice of what to use. Chapter 5: Message-Based Communications In Windows 3.1 Microsoft introduced the EnableCommNotification( ) function which adds a new message to the Windows system. This is as close as Windows gets to interrupt driven communications procedures, as the Comms system is interacted with in response to a received message rather than having to be polled in the message loop. Chapter 5 gives an overview of how this system works and then adds the facility to TSMCOMM.DLL, and modifies the application to give the user the choice of system. Chapter 6: Modem Communications Chapter 6 starts with a look at where modems fit into serial communications systems. The command set for Hayes modems is then examined and a modem API is implemented in MODEM.DLL, which includes setup, dial and hangup routines. A modem commands section is then added to the main application. As an extention of this, a dialing directory is implemented, where the user can store the names of systems, their phone numbers, plus the port settings: Baud rate, start, stop and data bits. This too, is then added to the main program. Chapter 7: File Transfer To enable the TSMTERM program to download files from the bulletin boards whose numbers are stored in the newly created address book, Monk adds the popular XMODEM file transfer protocol. The chapter starts with a discussion of the protocol; the packet, error checking, transmission, reception, errors and timeouts. The protocol is then implemented in another DLL, and - 19 - has options for using checksum or CRC error correction. These functions are then added to the main program. Selection of the file for transmission is carried out by use of the Common Dialog Box "Open File", so use of this feature of Windows 3.1 can be followed by examining the source code. Appendix A: Windows Serial Communications API Reference As mentioned above, this provides the documentaion for the functions and structures provided by Windows for communications systems. It includes the Windows 3.1 amendments and marks where the Window 3.1 implementation is different from 3.0. Appendix B: TSM Communications API Reference. This provides a full function by function description of Monk's own API, as used in the TSMTERM program. It describes the functions created throughout the book which are in TSMCOMM.DLL, MODEM.DLL and XMODEM.DLL. It is a useful summary and reference section. Appendix C: ASCII Character reference. This is a table in two parts. Part one gives the decimal value, hex value, display character and description for all of the 7 bit basic ASCII characters. Part two provides the same information for the extra members of the extended PC set. I found the book helpful and easy to follow. The layout is a bit strange, with funny bits of ornamentation around the edges of the pages, but you soon get used to it. It goes from communications basics to quite an advanced program in only 413 pages (including the appendices) so it obviously doesn't hang around when explaining things to make sure you have got a particular point. But that's the nice thing about books; if you find you have missed something, you can always go back and re- examine it. As a practical introduction to Windows Communications, I would heartily reccommend this book. There may be others that do the job too, but if there are I haven't found them and what I see here, I like. I confess that I haven't used the program, TSMTERM, and nor am I likely to; that's not why I bought the book. I wanted something that dissected a Windows Comms program, so I would know how to do it for myself, and that's what I seem to have got. As I mentioned at the outset, my own project has been "back burnered" for a while, but I have no qualms about tackling the Comms aspect head on. - 20 - Dialog boxes are another matter though. Ah well, back to Petzold... The author can be reached on CompuServe at 100063,1205. - 21 - Pascal in 21 Steps (A Beginner's Column for Turbo Pascal for Windows) By Bill Lenson Step Two - Divide and Conquer REVIEW Last month we talked about how to write a simple Pascal program. We showed you what variables and commands are. We also covered basic data types like integers, characters, and strings. Even though the examples where quite simple and didn't do anything practical, the purpose of that column was to introduce the basic program structure. This month we pick up the pace quite a bit. By the end of this column you should be able to read input from the keyboard, decide if the input is valid, repeat actions over and over, and break your code up into chunks called functions and procedures. All this and we'll even show you how to write a simple calculator. KEYBOARD IN / SCREEN OUT Reading information from the keyboard is not that difficult. There are some tricks to it though to make your life easier. Last month you used the 'writeln' statement to write text to the screen. To read text you use 'readln'. Readln (pronounced 'read line') pauses execution of your program until you type something and press the key. For example, Readln(s); lets you type in some text and when you press , puts it into the variable s. s can be any data type but some are better than others. If the s 'parameter' to the readln 'procedure' is an integer then you are only allowed to enter numbers. If it's a string type you can enter anything. When I say 'only allowed to enter a number', I mean that you can type in non-numbers but the program will crash when you press . This is bad. Let me illustrate. Type in the following program. program Bad_Code; uses WINCRT; var num : integer; - 22 - begin readln(num); writeln(num); end. When you run this program a window will be created and the cursor will flash. Type in the number 23 and press . Sure enough, 23 gets assigned to the num variable and gets displayed in the window by the writeln(num) statement. Now, rerun the program but type in Hello and press . CRASH. Pascal tries to convert Hello into an integer so it can store it into the num variable. Since it doesn't know how to do this it bombs. It's a poor program that lets the user crash it so easily. Try to make the next program blow up. Bet you can't (poor bet, there is a way but I'm not going to tell you how until next month). program Better_Code; uses WINCRT; var numstr : string; begin write('Please type a number and press : '); readln(numstr); write('The number you typed was : '); writeln(numstr); end. No matter what you type in the program spits it back onto the screen. The only thing limiting in this code is the programmer has no way to control what the user types. You can't be sure the user types a number in the above example. We'll show you how to do that in the next section. DECISIONS, DECISIONS Quite often you want a program to do one thing when a certain input is typed in and do something else when another thing is entered. The most common way to do this in Pascal is the IF statement. Before I show you the IF statement, let me first show you how you can convert a number stored as a string into an integer data type. Why we need to do this will become clear shortly. Try the following code. program String_2_Number; uses WINCRT; var numstr : string; - 23 - num : integer; errcod : integer; begin write('Please type a number and press : '); readln(numstr); val(numstr, num, errcod); writeln('The number you typed was : ', num); { Notice we combined two write statements into one by passing } { two parameters separated by a comma. } end. This program is similar to those above but if you don't enter a number a zero is printed instead of what you typed in. The val procedure tries to convert a string into a number, and if it can't it puts a zero in num and a number greater than zero in the errcod parameter. Another thing to note about the above program is the stuff enclosed in { and }. These are comments and allow the programmer to describe what is happening in the code. All programs should have comments that let other programmers (or yourself) look at a program and tell what the code does. You can put anything in a comment as long as it starts with a { and ends with a }. As an aside, sometimes you'll see older Pascal programs with comments that start with a (* and end in a *). These work too but the convention these days is to use the braces. I've been talking for a while about numbers and strings and how we want a number but we need to enter a string and then convert it to a number. What's all the hubbub about numbers and strings? Why not just leave them as strings? The answer is in the operations you can do to different data types in Pascal. For instance, you can't add two numbers together if they're stored in a string. Also, you can't concatenate (stick one string on the end of another) two numbers together if they're stored in integer types. Pascal is a typed language meaning that data is always stored in variables of a certain type. Each type has it's own set of built-in operations. Some string operations convert a string to upper case letters from lower case, extract some characters from the middle of a string, convert a string to a number, and so on. Most integer functions are math based such as adding two numbers, subtracting two numbers, multiplying two numbers, and so on. This section is about Pascal decision making and program flow control using the IF statement so let's give a simple example. - 24 - program Better_String_2_Number; uses WINCRT; var numstr : string; num : integer; errcod : integer; begin write('Please type a number and press : '); readln(numstr); val(numstr, num, errcod); if (errcod <> 0) then writeln('ERROR: The number you entered was invalid.') else writeln('The number you typed was : ', num); end. The IF statement needs only a little explanation. First the '<>' in Pascal means 'not equal to'. Also, there are no semicolon (;) before else's since the whole if..then..else combination makes a complete statement and last month we said that semicolons only come at the end of a statement (ugh!). So, the above is described as, 'If errcod is not equal to zero then write an error message else write the number entered to the window'. In the above, the stuff (I didn't say this was fine literature) between the IF and the THEN is called the 'condition'. Conditions are logical statements where we compare things. The things we compare are usually either two variables or a variable and a value like we do in the above code. Let's assume we have two variables var1 and var2. Some simple 'logical operations' we can do are: Logical Operation Tests if... ================= ===================================== (var1 <> var2) ==> var1 not equal to var2 (var1 = var2) ==> var1 equals var2 (var1 > var2) ==> var1 is greater than var2 (var1 >= var2) ==> var1 is greater than or equal to var2 (var1 < var2) ==> var1 is less than var2 (var1 <= var2) ==> var1 is less than or equal to var2 We'll use logical operations with IF statements many times in the future so don't worry if they look a little strange right now. Let's now move on to see how we can repeat something over and over. LOOPS - 25 - The process of repeating something in Pascal is called looping. There are four ways to loop but only two of them are commonly used, one seldom used and one is absolutely forbidden to be used. The last two will be covered at a later time. This section presents the first two: the FOR and WHILE loops. The FOR loop repeats some block of code a fixed number of times. Let's give a simple example: program For_loop; uses WINCRT; var x : integer; begin for x := 1 to 50 do write('Hello '); end. This program writes 50 'Hello''s on the screen. It's much simpler than typing out 50 write statements. The FOR statement is read 'for x equals one to fifty do write hello'. In other words, x is assigned the number one. The next statement is executed (the write). x is increased by one and checked to see if it's greater than 50. If it is, the FOR statement is finished and the statement after the write is executed. If it isn't, the write is executed and then x is increased by one and checked to see if it's greater than 50. And so on. In other words we repeat the write 50 times while we increase x from 1 to 50. On the 51st loop, the FOR is exited. Let's try another. program For_loop_2; uses WINCRT; var x : integer; begin for x := 1 to 10 do writeln(x); end. This example loops 10 times and writes the contents of x each time through the loop. In other words, the numbers 1 to 10 are written on the screen. The starting number doesn't have to be 1, we could have said: for x := 5 to 15 do writeln(x); which would write the 10 numbers from 5 to 15 on the screen. - 26 - The WHILE loop is a little different. WHILE will repeat until some condition is met. For example: program While_loop; uses WINCRT; var x : integer; begin x := 1; while (x <= 10) do begin writeln(x); x := x + 1; end; end. This program acts just like the For_loop_2 program above. In it we assign the value one to the x variable. Next, while x is less than or equal to the value ten (it starts at one so it is less than 10) repeat everything between the begin and end statement. What's between the begin and end is the meat of this WHILE loop. In it we write the x variable to the screen and then we add one to x. That adding one to x looks a little funny. We say 'x is assigned the value of x plus one. In other words, we do the stuff on the right of the ':=' and then put the result in the variable on the left of the ':='. If we said 'x := x + 2' we would see every other number from 1 to 10 (i.e., 1, 3, 5, 7, 9). FOR loops are often used when we know in advance how many times we will be looping. WHILE loops, on the other hand, are used when we don't know how many times we'll be looping. In common programming practice, we would typically use FOR loops to initialize array variables (a future topic), or repeat something that never changes in the number of iterations. WHILE loops are commonly used for reading input from users or files. For example, reading keyboard input until the user types some exit command the program knows about. We'll illustrate each looping technique in more detail next month. HINDSIGHT is 20-20 in PASCAL Before we dive into a larger program, I thought I would point something out about Pascal that beginners must know. Statements in Pascal cannot see things written after the current line. On the flip side of the coin, statements can only see things in the code that occur on lines previous to the current one. In other words, when the compiler compiles your code into machine language, it starts at the top and remembers each line as it comes across it. This is while variables are declared BEFORE - 27 - they are used. In the future you'll see that procedures and functions must be made before they are used. As an example: var x : integer; begin x := 3; y := 27; ... The x:=3 works because x has been declared as an integer before it was used. The y:=27 causes a compile error because y has not been declared yet. It may be declared some time later in the program but that would be too late. Why I'm bringing this up now is it should help a little bit when we take a look at procedures and functions. For now, just declare variables in the var section before you use them in code. THE CALCULATOR - ATTEMPT #1 Let's take a little break from learning the language to try to apply what we've learned up until now. The best way to do that is by writing an example program. From past experience, the quickest way to learn a language (or a new language) is by writing example programs. You must type these programs in and test them for yourselves for maximum learning benefit. In this section we'll try to build a simple calculator. It won't do a heck of a lot but I'm sure it will make understanding of the language easier. OK, so what's this calculator supposed to do? The first thing is it should ask the user for a number, an operation to perform, and then another number. Once these have been entered, the program will spit out the answer. The operations it can perform are addition (+), subtraction (-), multiplication (* ), and division (/). One catch to watch out for is division. You don't want the user dividing by zero as this is an error and will cause your program to crash. You therefore need to print an error message yourself if they try to divide by zero. Overwhelmed? Don't worry. There are ways to look at problems and break them down into smaller problems that are more manageable. Let me show you the program and I'll explain what it does. program Calculator_Number_1; uses WINCRT; - 28 - var numstr1 : string; numstr2 : string; operation : string; num1 : integer; num2 : integer; errcod : integer; begin writeln('Calculator Example #1'); writeln; write('Please type a number and press : '); readln(numstr1); write('Please enter the operation to perform (+, -, *, /) : '); readln(operation); write('Please type a number and press : '); readln(numstr2); val(numstr1, num1, errcod); val(numstr2, num2, errcod); if (operation = '+') then writeln('Answer: ', num1+num2) else if (operation = '-') then writeln('Answer: ', num1-num2) else if (operation = '*') then writeln('Answer: ', num1*num2) else if (operation = '/') and (num2 <> 0) then writeln('Answer: ', num1 div num2) else writeln('Error in input. Program ending.'); end. You should be able to read through this program if you take it slowly line by line. The first thing we do is declare all necessary variables. Then we ask the user for each number and an operation. The user must press after each input. Next, we convert the number strings to integers. Notice we don't check for an error in converting the number strings, numstr1 and numstr2. We don't care what they enter. If they enter something invalid the integer we get from the conversion is just zero. It's left as an exercise for the reader to put in checks to test for invalid entries, report the problem and exit the program. TIP: You might want to look up the HALT procedure if you want to try this exercise. OK, after the numbers are converted we perform the series of IF statement tests to perform the appropriate operation. Check - 29 - out the last test for division. The condition statement in that if has two conditions: (operation = '/') and (num2 <> 0). If we were to read the last IF statement, it would be described something like: 'if the operation is division and the num2 isn't 0 then write the answer, else write an error message'. Words like 'and' which separate conditions will be discussed in more detail in upcoming months columns. The programs are starting to look a little messy and I'm not using any comments (a bad habit but it won't help you to learn if I always tell you what I'm doing). We need to tidy things up a bit. PROCEDURES A typical Windows program can be several thousand lines long. If we have one big mainline, things will get beyond messy. Can you imagine thousands of statements between one begin and end? I can't. Programmers get around this problem by breaking up the code into chunks and giving a name to each piece. These 'chunks' are called procedures and functions. Suppose the mainline of the calculator program above was written as: begin Display_Title; Get_Inputs; Convert_Strings_to_Numbers; Display_Calculations; end. Isn't this a little more intuitive? All we need to do is tell the program what each of these procedures does. This is simple in this case. We simply take the groups of code that goes with each procedure and presto. Here's the next calculator. Notice it's a little bigger but it's also broken up into nice manageable sections. Also notice, the procedures are written after the variable declarations but before the mainline. This way, the procedures can 'see' the variables because they're declare before it, and the mainline can 'see' the procedures because they're declared before it. program Calculator_Number_1; uses WINCRT; var numstr1 : string; - 30 - numstr2 : string; operation : string; num1 : integer; num2 : integer; errcod : integer; procedure Display_Title; begin writeln('Calculator Example #1'); writeln; end; procedure Get_Inputs; begin write('Please type a number and press : '); readln(numstr1); write('Please enter the operation to perform (+, -, *, /) : '); readln(operation); write('Please type a number and press : '); readln(numstr2); end; procedure Convert_Strings_to_Numbers; begin val(numstr1, num1, errcod); val(numstr2, num2, errcod); end; procedure Display_Calculations; begin if (operation = '+') then writeln('Answer: ', num1+num2) else if (operation = '-') then writeln('Answer: ', num1-num2) else if (operation = '*') then writeln('Answer: ', num1*num2) else if (operation = '/') and (num2 <> 0) then writeln('Answer: ', num1 div num2) else writeln('Error in input. Program ending.'); end; begin Display_Title; Get_Inputs; - 31 - Convert_Strings_to_Numbers; Display_Calculations; end. Voila! Procedures are just like a little program within a program but they don't get executed until you call them as we do in the calculator mainline. The mainline is always the first thing that gets called in a program. It gets called automatically when the program is started. All other procedures and functions get called from the mainline. When looking at a program for the first time, skip down to the mainline and see what procedures are called. Some may be predefined in the language such as writeln and others may be user defined as in Display_Title. Usually each procedure will have a lot of comments at the beginning of it. Even though they are often small, it's still best to make the reader aware of what each procedure does. It's also good practice to put a ton of comments at the beginning of the program to describe what it does (a sort of program overview). It's interesting to note that a procedure can call another procedure. Such is the case in Display_Title for instance. Display_Title calls the procedure writeln. We can write our own procedures and have another one call it as long as it's declared BEFORE the one calling it. For instance, we could have called Display_Title from Get_Inputs but it didn't seem logical to me to do that. We could have though. This chaining of procedures is a very powerful feature indeed. FUNCTIONS A function is almost exactly the same as a procedure. The only difference is a function 'returns' a result whereas a procedure simply gets called and carries out it's instructions. What do I mean, 'returns a result'? Well, let's look at math functions such as square root of a number, the sine of an angle, etc.. Then there's string functions such as get the string length, concatenate two strings together, etc.. There are conversion functions such as convert a string to a number, convert a number to a string, etc.. Functions do something and then return some result. Function results have to be of a data type such as integer, string, char, or any other type. One interesting characteristic of functions is they can be used in expressions. Expressions are the right side of assignments (:=) or parameters to functions or - 32 - procedures such as writeln. Let's give a couple examples using the SQRT (square root) function. x := SQRT(9); {calculate the square root of 9 and assign that value to x} x := SQRT(y); {calculate the square root of the contents of y and assign to x} writeln(SQRT(y)); {write the square root of y to the screen} Some functions can be very simple (such as add the number three to a variable and return the result), or may be more complicated (such as a function used to calculate mortgages). Let's show how to write one of the simple ones this month - a function that simply returns the math number PI divided by 2. I think you should be getting the jist of how to write a program show I'll only show a function and how to call it. function PI_DIV_2 : integer; begin PI_DIV_2 := PI div 2; end; ... begin ... writeln(PI_DIV_2); ... x := PI_DIV_2 + 4; ... end. Instead of procedure, we start a function with the word function. Also, we must tell what type of value will be returned. That's the ': integer' part of the header. To tell the function to return the desired value, we appear to assign a variable with the same name as the function with the return result (in this case, the return result is 'PI div 2'). Once the function is defined, you can use it any way you would a predefined function. NEXT MONTH - 33 - To wrap up, functions and procedures are very powerful additions to the Pascal language. We've touched on them here a little but you will see them over and over in the future and I'm sure you'll get very used to using them. Next month I'll show how to pass a parameter to a function you define, just like SQRT takes one parameter and returns a result. I'll also show more data types, arrays, constants, introduce you to controlling the compiler in your code, and perhaps even how to write a simple game. See you then! If you wish to contact me, please do so at the following e- mail addresses: From Send e-mail to =========== =============== Compuserve: >INTERNET:bill.lenson%canrem@uunet.ca Internet: bill.lenson%canrem@uunet.ca - 34 - Hacker's Gash By Dennis Chuah This article contains a few of the tips, tricks and traps from my Windows Programming laboratory book. As I mainly use Borland C++, most of the discussion here will be slightly biased to Borland C++. However some issues also concern Windows programming in general. It is sad that the Windows API documentation[1] leaves out certain bits of information that is sometimes crucial to the correct usage of the API. Take MakeProcInstance for example. The documentation states this function must only be used to access functions from instances of the current module. This implies that MakeProcInstance will create a procedure instance given a hInstance and an exported procedure address provided it is called from the task that is associated with the hInstance. This is not always the case. Take a look at the following code extract: Somewhere in the application ... void CALLBACK SomeProc (void); . . FARPROC lpfnSomeProc; lpfnSomeProc = MakeProcInstanceForMe (hInstance, (FARPROC) SomeProc); if (lpfnSomeProc == SomeProc) MessageBeep (0); And somewhere else in the application's DLL ... FARPROC WINAPI MakeProcInstanceForMe (HINSTANCE hInst, FARPROC lpfnFarProc) {return MakeProcInstance (hInst, lpfnFarProc); } MakeProcInstanceForMe will return the address of SomeProc instead of the procedure instance. This is because MakeProcInstance will always return the same procedure address passed to it when it is called from a DLL, regardless of what hInstance it is passed, a point missed out in the documentation. Note: the documentation merely states that MakeProcInstance is not required for library modules. Hence: Trap: Never use MakeProcInstance to create a procedure instance from a DLL. Always create the procedure instance first before passing it to a DLL. Tip: Use "smart callbacks" prologue and epilogue code if your compiler supports it. This usually assumes DS is equal to SS. - 35 - For most applications this is true, except if you swap data or stack segments. Callback procedures compiled with "smart callbacks" do not need procedure instances to bind the data segment. The compiler just copies SS to DS in the prologue code. As there is no need for procedure instances, there is no need to list it in the EXPORTS section of the module definition file. Tip: Callback procedures in DLLs need not (and should not) use MakeProcInstance to create procedure instance. The compiler always assumes that DS is not equal to SS and there is only one data segment. It will load the correct data segment. Still on the subject of Windows API, there is a very little known (and very little documented) header file in Borland C++'s Include directory called WINDOWSX.H. A wealth of macros can be found in this header file. For example, the GlobalAllocPtr macro API allocates memory on the global heap, locks it and returns a pointer to the locked memory. This is similar to the malloc function that C programmers are more familiar with. Using GlobalAllocPtr also saves from having to keep track of a handle. Simple call GlobalFreePtr when the block of memory is no longer required. Note: Locking memory has its history in the real mode version of Windows. In protected mode, locking memory no longer has any meaning so it is all right to keep a block memory locked for its lifetime. Trick: The way GlobalAllocPtr is defined can cause the compiler to generate a "Code has no effect" warning. To get around this, place a void typecast over GlobalAllocPtr when you call it. For example: DWORD far *lpDword; . . lpDword = GlobalAllocPtr (GHND, sizeof (DWORD) * 20); . . if (lpDword != NULL) (void) GlobalFreePtr (lpDword); WINDOWSX.H also contains declarations for the Control Macro API. Using this API instead of sending control messages makes your code easier to read. Edit_SetTabStops (hEdit, nTabs, lpTabs); is more legible than; SendMessage (hEdit, EM_SETTABSTOPS, (WPARAM) nTabs, (LPARAM) (const int far *) lpTabs); - 36 - In addition, WINDOWSX.H also contains declarations for message crackers. By using these macros, you will make your code more portable. I have one criticism though, the macros are not defined to allow a pointer to be passed to the message handler functions. If you haven't used WINDOWSX.H before, I strongly encourage you to start now. Metafiles: A metafile is a series of GDI calls. It can be stored as a physical disk file or represented by a handle to a metafile (stored in memory). Tip: Use a drawing package (such as Corel Draw) to generate metafiles that can be included as a user-defined resource in your Windows application. This way, you can draw graphics using the powerful drawing tools of such packages and include the graphic in your application. Tip: Including a metafile as a user-defined resource in an application: Say the metafile name is METAFILE.WMF. ... somewhere in the resource header file (say RES.H): /* assign an ID value for metafile resource types */ #define METAFILE 2000 #define MYMETAFILEID 100 and somewhere in the resource definition file: #include "res.h" . . MYMETAFILEID METAFILE "metafile.wmf" To use the metafile: #include "res.h" . . HGLOBAL handle; HMETAFILE hMetafile; HDC hDc; RECT rect; . . /* Load the metafile */ handle = LoadResource (hInstance, FindResource (hInstance, MAKEINTRESOURCE (MYMETAFILEID), - 37 - MAKEINTRESOURCE (METAFILE))); /* no need to retrieve pointer as we only need the handle */ LockResource (handle); hMetafile = (HMETAFILE) SetMetafileBitsBetter ((HMETAFILE) handle); UnlockResource (handle); /* Then draw it */ hDc = GetDC (hWnd); GetClientRect (hWnd, &rect); SetMapMode (hDc, MM_ANISOTROPIC); SetViewportExt (hDc, rect.right, rect.bottom); PlayMetaFile (hDc, hMetafile); ReleaseDC (hWnd, hDc); /* Clean up */ DeleteObject (hMetafile); FreeResource (handle); It is important to set the mapping mode to MM_ANISOTROPIC and the viewport extents to rectangle in which the metafile is to be drawn. This ensures that the whole metafile is drawn inside the specified rectangle. However, some metafiles behaves badly and will draw outside the specified rectangle. Most metafiles sets the window extent and origin, and draws within the limits of the coordinates specified. However, this behaviour cannot be guaranteed for all metafiles. Trap: Forgetting to call LockResource can cause SetMetafileBitsBetter to return NULL. Trap: Metafiles with placeable headers cannot be processed by Windows so make sure you save your metafile without the placeable header. Miscellaneous: Trap: Be very careful when allocating memory in a DLL. Memory allocated by a DLL belongs to the application that called the DLL. Passing the handle or pointer to the block of memory to another application will most likely cause a GP fault. If a DLL needs to share memory between applications, use the GMEM_SHARE flag when calling GlobalAlloc. This, however, poses another problem. Memory blocks allocated with the GMEM_SHARE flag, although can be shared, still belongs to the application that allocated it and will be freed when the application terminates. If another application calls the DLL to access the memory after the application that allocated the memory terminates, a GP fault - 38 - will occur. The following diagram better illustrates this (see the Help file - Ed.). Tip: So, the lessons here are: a) if possible, let the application allocate memory and pass the handle/pointer to the DLL b) if the DLL must allocate memory, make sure it is used only for the application that caused it to be allocated c) if the DLL has to allocate memory that will be shared between applications, write a small application that allocates memory on behalf of the DLL. This application has to be active for at least the life of the DLL. (See accompanying article -- Allocating Shareable Memory (in next month's issue - Ed.)) Notes: [1] - I define Windows API documentation to be information documented in Windows API Vols 1-3. I know there are other sources of information, such as the numerous articles from Microsoft. However, it is my opinion that any information that is not included in a function's entry in the Windows API manuals should be classified as "undocumented". If the set of manuals are to be the primary source of reference material for Windows programming, it would be safe to assume that information documented elsewhere is not widely accessible to Windows programmers and therefore undocumented. About the author: I am currently doing a PhD. degree in Electrical And Electronic Engineering at the University Of Canterbury. My programming experience dates back to the days when "real programmers" coded with Z80 machine code. For the past two years I have taken an interest in Windows programming. Please send any comments, questions, criticisms to me via: email: chuah@elec.canterbury.ac.nz or post mail: Dennis Chuah c/o The Electronic and Electrical Engineering Department University of Canterbury Private Bag Christchurch New Zealand. - 39 - All mail (including hate mail) is appreciated. I will try to answer all questions personally, or if the answer has general appeal, in the next issue. If you are sending me e-mail, please make sure you provide me with an address that I can reach (most Internet and Bitnet nodes are reachable, and so is CompuServe). - 40 - Book Review Title: ObjectWindows How-To Author: Gary Syck Publisher: Waite Group Press By Rodney M. Brown The other day, I was in my local WaldenBooks bookstore, browsing over the crop of C/C++ books. One book that caught my attention was "ObjectWindows How-To". This book offers hints/tips on programming in Borland C++/Turbo C++ with ObjectWindows. The book is organized by problems, with the solution or "How-To" following. Each solution is graded in complexity; Easy, Moderate and Hard. After the solution is explained, it is followed with the author's C++ code. The C++ code is great for beginners. The author placed comments throughout the code explaining what each function does. The author also wrote a 2-3 page explanation of the code he wrote. This book is a great reference. It is filled with solutions to printing, changing the appearance/color of windows, etc. Below is a list of all the problems that are explained in the book: 1. Making An ObjectWindows Application. 2. Changing the Class Information & Style of a Window. 3. Adding control windows to a window. 4. Adding menus to a program. 5. Creating an MDI application. 6. Making and Using DLLs. 7. Using the TDialog object class. 8. Making a file selection dialog. 9. Making a text search dialog. 10. Using text controls. 11. Using list box and combo box controls. 12. Using check box and radio button controls. 13. Using string tables. 14. Converting programs to another language. 15. Loading different menus from a resource file. 16. Adding and Deleting menu items. 17. Changing the attributes of a menu item. 18. Changing the check marks in menus. 19. Using bitmaps as menu items. 20. Creating pop-up menus. 21. Creating a text editor. 22. Getting input from the keyboard. 23. Getting input from the mouse. 24. Using the serial port. 25. Using the sound interface. 26. Handling the printer. - 41 - 27. Drawing in a window. 28. Drawing different shapes. 29. Using bitmaps. 30. Using fonts. 31. Using metafiles. 32. Using the color palette. 33. Making 3-d dialog windows. 34. Creating custom controls. 35. Making a screen saver. 36. Making nonrectangular windows. 37. Using complex shapes as windows. 38. Creating a file object. 39. Saving different objects in a file. 40. Reading and writing parts of the file. 41. Creating a DDE server. 42. Creating a DDE client. 43. Getting updated data from the server. 44. Executing commands on the server. 45. Getting Drag-and-Drop files. 46. Creating an OLE client. 47. Creating an OLE server. I hope I didn't bore you listing all of the subjects covered in the book. It is a great reference book for Borland C++/Turbo C++ ObjectWindows programmers. I predict that this book won't spend much time on my bookshelf. Contacting me online: CompuServe: 72163,2165 America Online: RodneyB172 You can also contact me at the following Internet addresses: CompuServe: 72163.2165@compuserve.com America Online: RodneyB172@aol.com - 42 - Creating Help Files By Todd Snoddy Like most of the readers of Windows Programmer's Journal, I enjoy Windows programming. Although I don't currently have the blessing?/curse? of being a "full time programmer", I do consider myself to be a dedicated and serious Windows programming hobbyist. I would like to use the opportunity that WPJ provides to share with you some of the tools I use to increase my productivity. As my busy schedule permits, I am hoping to be able to review different programming utilities in upcoming issues. I would like to be able to concentrate mainly on both shareware and free software. Although I don't have anything against commercial software, many of the software packages sold commercially are rather expensive, putting it out of the budget of most hobbyists or small business users. In addition, shareware has many advantages besides usually being less expensive. Many of the shareware products available fill a void that isn't quite covered by the major software publishers because they don't take the time to develop something that won't have mass market appeal. I'll try to point out some of these products in the future. Also, the quality of modern shareware can often approach or surpass that of commercial software. My review topic this month will be Windows help files. I will cover a few tools that I use to develop my help files with, and actually present a sample help file that can be used as an example for your own help files. The tools that I will discuss are free, and are available from various sources, including CompuServe and numerous Internet ftp sites. Before I get started with that however, I would like to take a brief moment to tell you a little about myself so that you can get an idea of my perspective. I am currently in the U.S. Army and stationed at Ft. Riley, KS. I work as a computer technician/soldier during the day, and in my free time I do a lot of Windows programming on personal projects. I am almost entirely self taught, and am competent in C, Pascal and assembly. I have been programming Windows for around two years now, and C for about seven years. Prior to that, I programmed BASIC for several years. As mentioned before, programming is a major hobby of mine, but I also like to read and play pool, usually 9 ball. Now to the good stuff. Currently, there is only one vendor who makes a compiler capable of creating Windows help files, and that is of course Microsoft. This help compiler takes a file that contains a special formatting language called Rich Text Format, or RTF, and converts it into a HLP file that can be used - 43 - by the WinHelp engine included with Windows. Contrary to what Microsoft would like to have us believe, it's not necessary to use an RTF word processor like their own (whose name we all know) to create Windows help files. Although the actual format of the HLP file is considered by many to be one of the best kept secrets in the industry, there are several low cost or free utilities available to generate the RTF file necessary for input to the Windows help compiler. When I first wanted to create my own help files, I looked at several utilities to see what was available, especially since I didn't have several hundred dollars extra to buy an expensive word processor. The one that I found that was overall easiest to use is RTFGEN, by David Baldwin. This is an excellent utility whose use is free, not even shareware. With David's permission, an older command line version is included with this issue of WPJ, along with the Pascal source code, although a newer Windows version is available for free as well. For a small fee, you can even obtain the source for the Windows version. RTFGEN's sole purpose is to convert standard ASCII text files into an acceptable RTF file recognizable by the help compiler. Using RTFGEN for creating your help files is almost like using a programming language like C. Many people say that C is a high level language with the flexibility of a low level language like assembler. RTFGEN will let you mix straight RTF code with your help source file for maximum control over the desired output, or you can use the default settings and just specify the actual topic information and let RTFGEN handle the rest. RTFGEN has some special keywords that will actually be converted into RTF automatically, so you don't have to get your hands dirty if you really don't want to. Creating a help file with RTFGEN involves a few steps. First, you need to plan the layout of the help file. The importance of this step can't be overstated. A well thought out layout makes a big difference in the apparent quality of your product, especially when the user is totally lost and in a blind panic tries to bring up the online help instead of opening the manual. The next step would be to create the help source files. For this task, I use a free Windows editor called the Programmer's File Editor by Alan Phillips, but it can be done using any editor that reads and writes standard ASCII files, like DOS's EDIT command, or even the Windows Notepad. The help source files contain the actual help topic information, including the topic text, keywords, and browse - 44 - sequences. These files will have the RTFGEN commands in them, and may also have RTF statements for added flexibility or control of the help file. Each help topic will be in a specific format, and this will be covered shortly. After creating the help source files, we are almost ready to compile them into a HLP file. However, before this can be done, the source files need to be processed by RTFGEN. This procedure converts the special RTFGEN commands into RTF automatically. If you have any actual RTF statements in the source file along with the rest of the text, it will be left alone and passed on unmodified. This allows the ultimate in customization of the help file. There is one final step required before actually compiling the help file. This is to create a Help Project file, or HPJ file. This file specifies certain options to the help compiler, including the source files for the help file, bitmaps, and any help macros used. I won't cover this subject in more depth here as it is documented in other places like the help compiler documentation. By the way, another excellent source of information on the format of the HPJ file as well as help file creation in general is the Help Author's Guide. This is a help file created by someone at Microsoft but is not supported officially by them. It is usually distributed as HAG.ZIP and can be found on Compuserve and Internet ftp sites such as FTP.CICA.INDIANA.EDU. It contains a complete reference for the RTF command syntax. That's all you need to create a help file. That's all? Well, as mentioned before, the help topics must be in a specific format. David Baldwin's documentation for RTFGEN does a good job of explaining how to use it, but I'll briefly cover some of the highlights. First, every RTFGEN command is started by a \ character, and most are terminated by a ` character. For example, to specify a topic title, the command would be: \title Selecting Background and Foreground Colors.` This statement specifies what text will be displayed in the Go To list box when the user tries to search for the keywords "background colors, selecting". See Figure 1 for an example (see the Help file - Ed.). In Figure 1, the text shown in the upper list box is specified by the topic keywords. To get the currently selected text to be used for the shown topic, the following statement would be used: - 45 - \keyword background colors, selecting` Every topic in the help file has to have some way to distinguish it as an individual topic and separate it from every other topic. This is known as the topic name. The topic name is never actually seen by the person viewing the help file, but is instead used as an internal marker to identify the individual topic to the help system. In RTFGEN, the topic is specified by a statement like: \topic SelectingColors` In the help source file, all of the above statements would be grouped together in the topic heading. The topic heading is separated from the rest of the topic text by using a minimum of five = characters at the end of the heading. In practice, this would look like: \topic SelectingColors` \title Selecting Background and Foreground Colors.` \keyword background colors, selecting` ===== This is the sample topic text for selecting a background and foreground color. ------ In the above example, the actual topic text follows the = character, and is terminated by five or more - characters. This is how RTFGEN determines the separation between topics, and based on their position, it will generate the appropriate RTF code for the help compiler. RTFGEN can be used to generate hypertext jumps or popup text as well. For example, if you have some concepts in a topic that the user may not be familiar with, you can designate certain keywords in the topic that the user can select on that brings up a popup window with a more detailed description or definition of the keyword. To define a hypertext jump with RTFGEN, the text that will be selected by the user to jump to the other topic will be enclosed in brackets along with the name of the topic to be jumped to. For example, Figure 2 shows a typical display of a hypertext jump (see the Help file - Ed.). To duplicate this affect using RTFGEN, the following statement would be used in your help source file. - 46 - [Choosing Fonts and Font Sizes:ChoosingFonts] In this example, the ChoosingFonts will represents the topic name that will be jumped to when the hypertext link is selected. If instead you wanted to use a hypertext popup window for a definition, then you would use two sets of brackets. An example of this is shown below (see the Help file for Figure 3 - Ed.). picture elements ([[pels:pels_def]]). Since I am a firm believer in the concept of explaining something and then giving an example of how to do it, a sample help source file is included, along with all of the required files to build it, except for the actual help compiler. You will need the Windows 3.1 version of the help compiler to build the sample. After studying the sample files, as well as the documentation for RTFGEN, you should be well on your way to creating your own help files. To compile the sample into a help file, use the command: SAMPLGEN sample This will invoke the SAMPLGEN.BAT batch file to convert your source file into RTF, and if everything goes ok, it will then run the help compiler. There are many ways to customize your help files, and I may cover some techniques in future issues of WPJ as time permits, but if you want to learn some techniques on your own, then beg, borrow, steal, or otherwise obtain a copy of the previously mentioned Help Author's Guide. You can't beat the price since it's free, and it is full of useful information on RTF and creating help files. I welcome any comments anyone may have on this article, as well as suggestions for other products to review in the future. I can be reached on America Online at TSnoddy, on Compuserve at 71044,1653, and on the Internet at tsnoddy@nyx.cs.du.edu. Although I'm very often quite busy, I will try to respond to any messages. Also, if you do decide to start using RTFGEN, please contact the author and let him know what you think of it. The world needs more software authors who are willing to make useful tools like this available for free, and when someone makes a contribution like that, receiving feedback from users provides a reason to produce other useful utilities too. The author, David Baldwin, can be reached on Compuserve at 76327, 53. - 47 - Gdi See Gdi Do Graphics Animation in Windows By Bernard Andrys In this series of articles, I will discuss the writing of a space invaders style game for Windows 3.1 in C. The articles will be directed to people who understand programming (such BASIC) and know a little about Windows but are new to Windows programming in C. Experienced C programmers will immediately recognize the poor style of my code. In some cases, I will be doing this on purpose. If a reader is new to C AND Windows, they're probably going to have just as much of a problem following the C code as trying to follow the Windows specific areas of programming. (I know that I sure had problems learning both at the same time.) As a result, I will try to stick with easy to understand syntax like "x=x+1" instead of the C shorthand "++x". Later articles will use more of the shorthand notation as I and the readers get comfortable with C. The other reason for the bad style is that my coding philosophy is "Well it works... so I guess its good enough." This doesn't mean, however, that I don't want to learn how to write C better. If you are an experience C programmer, then please drop me a line about how I could improve my programming style. What will be different is how we go about writting the program. I think that the best way to learn programming is to write a working program, no matter how poorly written, and then refine it as you learn more. So I will start with the standard "do nothing" windows program that just creates a window. Future articles will take this basic program and add the missle base, missle firing, invaders, invaders firing, and of course the ufo. From this simple program we add features and fix problems in future articles. Some of the improved features will be 256 color bitmaps and digitized sound. A couple of the problems that we will need to deal with are friendly multitasking and the efficient drawing of bitmaps. Once the game is finished, I would like to write about converting the program to C++. If you are new to Windows programming in C then before you go on to read the program below, you'll need two books as references. The first is "The C Programming Language" by Dennis Ritchie and Brian Kernigan. The second is "Programming Windows" by Charles Petzold version 3.0. These two books have all the basics of C and Windows programming in C. If you are knowledgeable about Windows programming in C then check out the "Duncan" format of my Windows C code. There's no case switch statement for processing messages. An array of - 48 - structures contains the list of messages that our program will respond to along with the function that is called for each message. A final note before the program starts: This program was written using Quick C for Windows. As far as I know, I didn't write anything that was compiler dependant (except the make file, but then I didn't write it, Quick C wrote it for me). If you have any problems compiling it, drop me a line so I can remove any compiler dependancies in future articles. I have been meaning to purchase Turbo C++ for Windows and/or Visual C++ so expect make files for one or both of those compilers in the near future. // START OF PROGRAM LISTING /* invid.c */ /* There are two types of ways of adding comments into C source code. The first way to add a comment is to put a / * before the comment and a * / at the end of the comment. (I had to add a space between the / and * because otherwise the C compiler would think that it really was a comment. You can't put comments inside of comments.) The other way to add a comment in your source code is to put a // at the start of the comment. The comment will start at the // and go to the end of the line. */ #include #include "invid.h" /* The #include statement allows you to include another file in your source code. This saves the hassle of cutting and pasting the code in. The file that you include is put inside a pair of < > signs or a pair of " ". Using < and > tells the compiler to search through the path defined by an INCLUDE environment variable (use SET INCLUDE = path to include files). If you use " " instead, it tells the compiler to only check the current subdirectory. You normally use < and > for files that come with with the compiler. Quotes are normally used for your own include files. Every Windows program needs to include . C by - 49 - itself doesn't have any functions for handling character input/output, character strings and other basic functions. Therefore most C programs will also include and to have a bunch of functions to work with. My own file "invid.h" contains my global variable declarations and function prototypes. You should already know that a global variable is a variable that all functions can share. In C, a global variable is created by declaring a variable above the function main() ( or for Windows, the function WinMain() ). Normally you put your global variables at the top of your program after the #include files (or in your own #include file). Global variables should normally be avoided because they make your program larger and make your code harder to manage as it grows. Function prototypes are a list of the functions that are in your program. A function prototype includes the data types that the function will return and the data types of the parameters of the function. If you were to write a function called DrawPixel(), you would write: int DrawPixel( int x, int y) { ...the function's code goes here } Int is the data type that the function returns. DrawPixel is the name of the function. X and y are the function's arguments with int x and int y saying that the arguments are of data type integer. To make a function prototype just copy: int DrawPixel (int x, int y) from your program, paste it at the top of your program (or put it in a separate #include file like I did with invid.h), and add a semi-colon to the end of the line. The function prototype for function DrawPixel(x, y) is: int DrawPixel (int x, int y); The function prototype tells the compiler about your DrawPixel function before its used. This lets the compiler check to see that you use your DrawPixel function with the right data types. */ struct decodeWord { WORD Code; LONG (*Fxn)(HWND, WORD, WORD, LONG); }; struct decodeWord messages[] = { - 50 - WM_CREATE, DoCreate, WM_DESTROY, DoDestroy, } ; /* I have used a different method for having a Windows program respond to messages than what is normally seen in Windows programming books. It is a method that I first saw used by Ray Duncan in PC Magazine. Instead of a large switch case statement, it uses an array of structures that list the messages that your program will respond to and the function that is called when that message is received. (I'm going to skip covering the details of arrays and structures in C for now. Otherwise, I wouldn't have anything to write about in future articles!) This format allows you to easily add new messages without having to deal with a long and messy switch case statement. To use the Duncan format, simply add the message that your program will respond to, a comma, the name of the function that will be called when you receive that message, and then another comma. In the case above, if we wanted to have our program respond to a WM_SYSCOMMAND message (this is the message sent when you click on the system menu of a window) we would make the following change to the lines above. struct decodeWord messages[] = { WM_CREATE, DoCreate, WM_SYSCOMMAND, DoSysCommand, // the Windows Message, Our Function WM_DESTROY, DoDestroy, } ; Now when Windows sends a WM_SYSCOMMAND message to our program, our program will run the function DoSysCommand. (I could have called the function anything I wanted.) We would then go to the bottom of the program code and write the function DoSysCommand(). */ int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow) /* This is start of the Windows program. (A regular C program starts with a function called main().) The keyword PASCAL tells the C compiler that this function will be called with the PASCAL calling convention. This means that the code that is written inside of this function has access to the actual variables that are passed as parameters (i.e., hInstance, hPrevInstance, lpszCmdParam, nCmdShow). A normal function in C doesn't let you change the original variable, it gives the function a copy of the variable to play with. If the keyword PASCAL was NOT used above, then hInstance would - 51 - contain a duplicate of the number that Windows uses to keep track of which copy of this program we have in memory. (In Windows, we can have the same program loaded several times.) If we changed the value of hInstance, it wouldn't change what Windows thinks hInstance contains. It would only change what our function thinks hInstance is. Using the keyword PASCAL means that if we change the value of hInstance, it will change what Windows thinks the value of hInstance is as well. This can be very dangerous so you should use care when dealing with the parameters to functions that are declared using the PASCAL keyword. So why use the PASCAL keyword? There are three reasons for using the PASCAL keyword. The first is that when writing parts of a program, it's often easier to use the keyword PASCAL to get access to the actual arguments than to have to deal with creating a group of global variables or use a pointer to the arguements to get to the original variable. The next reason is that the PASCAL calling convention for functions makes programs a little smaller and makes them run a little faster. The last reason is that any function that Windows will call in your program must be declare as a PASCAL function. A simple Windows program only has the functions Winmain() and WinProc() called by Windows. Winmain() is the start of your program and WinProc() is the name usually given to the function that Windows calls when it has a message for your program. A function that Windows calls inside of your program is called a callback function and all callback functions must use the PASCAL keyword. I won't spend any time here discussing callback functions because using them is a lot more messy than just adding the keyword PASCAL to the function's declaration. */ { MSG msg ; /* Declare a variable msg of data type MSG (message). This will be the variable that will hold any messages that are sent to our program. */ if (!hPrevInstance) InitApplication(hInstance); /* hPrevInstance is a number that Windows passes to WinMain as a parameter when run. The number identifies the previous copy of our program that is currently running. If this is the only copy of the program running, hPrevInstance contains 0. In C, the if() statement executes the next statement if what is in the parenthese's is true (and in C, true is anything except 0). The ! operator in C performs a logical NOT - 52 - operation. A logical NOT operator changes a 0 to a 1 or a 1 to a 0. Therefore if hPrevInstance is 0 (no previous instance running) then !hPrevInstance equals 1 (True). So if !hPrevInstance is true then "if (!hPrevstance)" runs the next statement which is InitApplication(). Once you've seen this a couple of times, you'll automatically translate a statement like: if (!hPrevInstance) InitApplication(hInstance); to mean: If there is not a previous instance of our program in memory then run the function InitApplication(). InitApplication is a function that we will write to setup the information for this window that can be shared between all copies of our program that are running. */ InitInstance(hInstance, nCmdShow); /* Call InitInstance(). This is the function that sets up the information for this program's window that is unique for this particular copy in memory. */ while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } /* This is the message loop that keeps our program running. Your program runs in this infinite loop as long as the function GetMessage() returns TRUE (In C, TRUE is anything other than 0.) The GetMessage() function returns FALSE if a WM_QUIT message is received. This means that the while loop will stop if our program receives a WM_QUIT message and our WinMain() program will continue to the lines below. */ return (msg.wParam) ; } /* This is the end of our Program. What follows are the functions that WinMain calls and the functions that we call when we receive a message as defined at the top of this program in the array messages[]. */ BOOL InitApplication(HANDLE hInstance) /* InitApplication will be the data that is shared between all - 53 - instances of our program. */ { WNDCLASS wndclass; wndclass.style = 0 ; wndclass.lpfnWndProc = WndProc ; /* For this program WndProc() will be the name of the function that windows calls when it has a message for our program. */ wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (hInstance, szAppName) ; /* Load our icon for this program. szAppName is a variable defined in invid.h. (this is just a short cut so that if I use this code to make a different program, I can just change the variable szAppName in invid.h instead of having to go through all my source code to see where I used the name of my program.) Take a look at the file invid.rc for the rest of the information about how you add an icon. */ wndclass.hCursor = LoadCursor (hInstance, IDC_ARROW) ; wndclass.hbrBackground = GetStockObject (BLACK_BRUSH) ; /* the background of our window will be painted black. */ wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szAppName ; return(RegisterClass (&wndclass)); } BOOL InitInstance(HANDLE hInstance, WORD nCmdShow) /* The InitInstance() function will initialize the data that will be unique for a particular copy of the program that is in running. */ { hWnd = CreateWindow (szAppName, "Invid", DS_SYSMODAL | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX, /* The flags DS_SYSMODAL etc. are constants defined in windows.h that tell windows how the window should look. An | symbol is used between the flags because it is the C operator for logical - 54 - OR. If you have the binary numbers 1000 and 0100 then 1000 | 0100 = 1100. So by putting an | symbol between each flag, we combine the different flags into a single number that Windows uses to figure out how the window should look. */ CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_WIDTH, WINDOW_HEIGHT , /* A normal Windows program would use the Windows constants CW_USEDEFAULT above. However, since this is a graphic game whose window cannot be resized (see DS_SYSMODAL flag above), we are going to define precisely the size of the window in pixels. WINDOW_WIDTH and WINDOW_HEIGHT are constants that I've defined in "invid.h". */ NULL, NULL, hInstance, NULL) ; ShowWindow (hWnd, nCmdShow) ; UpdateWindow (hWnd) ; return (TRUE); /* In TRUE is defined as 1 and FALSE is defined as 0. */ } long FAR PASCAL WndProc (HWND hWnd, WORD wMsg, WORD wParam, LONG lParam) /* This is the function that Windows calls whenever it has a message for our window. WinProc goes through each item in the array messages[] (which was defined at the top of our program) and checks to see if the message it received matches the message in the array. If the message does match, then it runs the function that goes with that message. */ { int i; for(i=0; i < dim(messages); i++) { if(wMsg == messages[i].Code) return((*messages[i].Fxn)(hWnd, wMsg, wParam, lParam)); } return(DefWindowProc(hWnd, wMsg, wParam, lParam)); } - 55 - LONG DoCreate(HWND hWnd, WORD wMsg, WORD wParam, LONG lParam) /* This is where we will load our bitmaps and initialize some of our variables. The program doesn't do anything yet so there is nothing to create. */ { return 0; } LONG DoDestroy(HWND hWnd, WORD wMsg, WORD wParam, LONG lParam) /* The program doesn't create anything yet so there is nothing to destroy before the program exits. */ { PostQuitMessage (0) ; return 0 ; } // END OF PROGRAM LISTING So that's it for this month. Don't you just hate all these programs that do ABSOLUTELY NOTHING. I sure do! Unfortunately this article is already a bit long so you'll have to wait until next month before we turn this "do nothing" Windows program into a nifty little space invaders style game. If you'd like a peek at where I'm going with this article, run peek.exe. (You will probably need to play with the delay= value in the invid.ini file that is created in your Windows subdirectory after the program is run the first time.) About the author: (short version) SWM 25 ISO SWF. Enjoys long walks, sun, surf, skiing. (long version) Bernard Andrys is a network engineer for CSI, an ISDN hardware and software development company. He holds a bachelor's degree in Mechanical Engineering from the University of Maryland at College Park. He can be reached through the Internet at andrys@csisdn.com or on the Windows Programmer's Journal BBS. - 56 - Book Review: Windows++ By Philip Sloss I started trying to write Windows programs the old-fashioned way -- copying examples from magazines and books. This is still my preferred method when it comes to things I don't understand clearly. ("Things I Don't Understand Clearly" -- this is such an all-encompassing topic, that brevity and disk space prevent me from fully explaining it; however, you will find examples sprinkled throughout the rest of this article.) I do like trolling the book stores looking for interesting examples on Windows programming, and one of my first finds was a book called "Windows++." "Windows++: Writing Reusable Windows Code in C++" Often, you can find out an important thing in the preface to a book or article: what the book is about. The preface of "Windows++" states that it is "essentially a recipe for building a C++ class library interface to Windows." What really sold me on this book was that the author, Paul DiLascia, actually *shows* how to do this. Instead of finishing the book with a jumble of ideas that you have to put together, Paul goes the whole twelve yards, and shows how to put the library together. The end result is that "Windows++" is not just a book on how to build a class library, it is a class library. Armed with (or, rather, disarmed with) a general naivete about Windows programming and a lack of enthusiasm for writing long repetitious code, I decided to use the Windows++ library to write Windows programs. The book and the library are mutually beneficial: not only does the library serve the book, but the book is the perfect reference for the library! When I'm having problems, I can see how something was implemented in the source code, and read a detailed explanation of it in the book. The book is also educational on Windows programming in general. A couple of points: First, class libraries are very helpful and can be of great benefit. Microsoft's Visual C++ is permanently entwined in their class library, the Microsoft Foundation Class Library (MFC). MFC is the primary reason that Visual C++ is so useful. Second, and just as important, I think that one still has to spend time learning Windows the "C" way. There is no way around it, in my smug, slight opinion. For example, I'll use someone I'm on speaking terms with -- me. Shortly after purchasing "Windows++," I went and got the bible -- "Programming Windows" by Charles Petzold. What happened with me was that I quickly began asking a lot of questions like "Well, how does this work?" (Compiler errors will cause one to - 57 - speak out loud to no one in particular.) It helped to know how Windows++ calls were mapped to the Windows API, because then I had some clue as to what to expect. The general consensus is that Mr. Petzold's book is the best there is on Windows programming fundamentals -- I agree. In other words, while I'm led to believe that class libraries like MFC and Windows++ can greatly simplify one's work, I still think most beginning Windows programmers are in for a future showdown with the Windows API. "Hello, World" The proliferation of the phrase "Hello, World" has extended beyond computer programming and now has completely taken over the information age. The standard introduction to just about anything -- renting a car, sky diving without a parachute (also known as sky jumping or sky plunging), underwater basket cooking -- is a square with the words "Hello, World" in the middle of it. On your first parachute-less sky dive, this is a good thing to aim for, as it will allow the authorities to easily locate your remains. Windows programming adds a system menu, title bar, and maximize/minimize buttons to the "Hello, World" square. In addition, it requires a rather detailed preparation of structures and calls to functions with 423 parameters. It is also, apparently, the basis for beginning a Windows class library. In C, the "Hello, World" program looks like this (adapted, with few alterations, from Petzold's "Programming Windows"; those of you who have seen this countless times before, please try not to become physically ill): //////////////////////////////////////// #define STRICT #include LRESULT CALLBACK _export WndProc (HWND, UINT, WPARAM, LPARAM); int PASCAL WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPCSTR lpszCmdParam, int nCmdShow) { HWND hwnd; MSG msg; WNDCLASS wndclass; static char szAppName[] = "Hello"; if (!hPrevInstance) { wndclass.style = CS_HREDRAW | CS_VREDRAW; - 58 - wndclass.lpfnWndProc = WndProc; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hInstance = hInstance; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION); wndclass.hCursor = LoadCursor (NULL, IDC_ARROW); wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wndclass.lpszMenuName = NULL; wndclass.lpszClassName = szAppName; RegisterClass (&wndclass); } hwnd = CreateWindowEx(NULL, // extended window style szAppName, // window class name "The Hello Program", // window caption WS_OVERLAPPEDWINDOW, // window style CW_USEDEFAULT, // initial x position CW_USEDEFAULT, // initial y position CW_USEDEFAULT, // initial x size CW_USEDEFAULT, // initial y size NULL, // parent window handle NULL, // window menu handle hInstance, // program instance handle NULL); // creation parameters ShowWindow (hwnd, nCmdShow); UpdateWindow (hwnd); while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } LRESULT CALLBACK _export WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; RECT rect; switch (message) { case WM_PAINT: hdc = BeginPaint (hwnd, &ps); GetClientRect (hwnd, &rect); DrawText (hdc, "Hello, Windows!", -1, &rect, - 59 - DT_SINGLELINE | DT_CENTER | DT_VCENTER); EndPaint (hwnd, &ps); return 0; case WM_DESTROY: PostQuitMessage (0); return 0; } return DefWindowProc (hwnd, message, wParam, lParam); } Again, all this gets you is a window that can be closed, with a line of text in it (although it does help pad out this article). And, for the most part, all programs have to have this code somewhere in them. This is where the class library comes in. A class library does repetitive things like filling the WNDCLASS structure and calling CreateWindowEx() with the correct parameters for you. You only have to define the styles and parameters specific to your application. What "Hello, World" looks like using "Windows++" For purposes of introduction, one of the first sections in the book shows what the Windows++ version of "Hello, World" looks like. It reduces the long, C-style program to this: #include // main Windows++ header file APPCLASS HelloWin : public WPMainWin { public: HelloWin() { createWin("Hello"); } void paint(WPPaintStruct &ps) { WPRect clientArea = this; ps.drawText(clientArea, "Hello, Windows++.", DT_SINGLELINE | DT_CENTER | DT_VCENTER); } }; void WPApp::main() { mainWin = new HelloWin; run(); } As the "Hello, World" example is a do-nothing program, this program has very little code -- a nice correspondence. For a detailed explanation of how this is orchestrated, I strongly and emphatically suggest purchasing "Windows++." - 60 - What's the Difference? What separates "Windows++" from some of the other books on C++ Windows programming is that "Windows++" is primarily about how the library fits together. Often, all one gets is the source code to a class library at the back of the book and/or on disk and instructions on how to use it. "Windows++" shows how anyone can build their own library -- it discusses some of the issues one has to deal with, and some of the standard and extended features one should consider incorporating in a class library. At the same time, there are several instructive examples of real Windows programs written with the Windows++ class library. And finally, in the last chapter, the reader is shown how to put the class library into a dynamic link library (DLL), and the issues and problems that any library has to deal with to put C++ classes into a DLL. "How About a Sudden Ending?" So, summing up: "long, redundant code -- bad, reusable C++ Windows classes -- good." And a good book on how to make a C++ class library for Windows is "Windows++." About the Author: An Unauthorized Autobiography of Philip Sloss Born from a crude genetic experiment mixing the genes of a human sperm with tree sap, Mr. Sloss has led a far from legal existence since his grudging inception into the world. After being developed in an artificial womb, he was raised in a large culture of athlete's foot fungus. Mr. Sloss was accepted into the aerospace engineering department at San Diego State University, and in May of 1992, having advanced to the rank of graduate, he left college and began a career in unemployment. He is now under under psychiatric observation for "behavior unsuitable to inert objects." Hospital officials report that Mr. Sloss' main hobby is spotting lint. - 61 - Getting in touch with us: Internet and Bitnet: HJ647C at GWUVM.GWU.EDU -or- HJ647C at GWUVM.BITNET (Pete) GEnie: P.DAVIS5 (Pete) CompuServe: 71141,2071 (Mike) WPJ BBS (703) 503-3021 (Mike and Pete) You can also send paper mail to: Windows Programmer's Journal 9436 Mirror Pond Drive Fairfax, VA 22032 U.S.A. In future issues we will be posting e-mail addresses of contributors and columnists who don't mind you knowing their addresses. We will also contact any writers from previous issues and see if they want their mail addresses made available for you to respond to them. For now, send your comments to us and we'll forward them. - 62 - The Last Page By Mike Wallace I got this in the mail a few days ago from Mike Strock... Here is a wonderful quote I think you will just love. It comes from the Journal American, page d6, in the 2nd article, 3rd paragraph in the 4th column. "Electronic bulletin boards are fast becoming a popular way to get copies of programs for free, but the practice is illegal. Bulletin boards are computers that can be reached by other computers via modem. Most of them require the new users to upload an illegal program onto their machines (thereby incriminating themselves) before they are allowed to down-load other programs for free, said Alison Gilligan, international anti-piracy specialist at Microsoft." Bulletin boards have been blamed for spreading illegal copies of software almost since the day they started appearing. Software companies have long complained their very existence is in danger due to these BBSs spreading free copies of their software. Is this fair to the majority of BBSs? No, I don't think so. I've been on a lot of boards, and most of the ones I've seen aren't doing anything illegal. Maybe you need to know someone to get onto the illegal boards, but that doesn't legitimize the above quote from Microsoft. The trend in this country to blame others for your own "bad luck" gets out of hand sometimes. Say you start up a commercial software company and nobody buys your app. Does that mean everyone has a free copy obtained from a BBS? Maybe, but there's also a chance your program isn't worth buying, or people don't even know about it. It's hard to buy every commercial program you want when each one may cost $200-300, or more. The software companies say this is to recoup their losses from illegal (unpaid) copies floating around. I think this may have the opposite effect - who can afford to buy software that's so expensive? Now, I'm not condoning software piracy. Besides being illegal and immoral, I think everybody loses when nobody's paying for their software. The concept of "shareware" should be extended to commercial software. Will this ever happen? Never mind whether it's even feasible. I would probably own more software if I could try it out first and then pay for it if I decide I'll use it. But as it is, I can't afford to pay big bucks for a program that I may never use once I've tried it out. So, the result is there's probably a lot of good software for sale that would make my life easier, but I'll never know about it. The same can probably be said for a lot of computer users. Hmmmm...sounds like - 63 - everybody's losing again. A lot of these companies only appear to be in the business of manufacturing software so they can make a lot of money, and I think the people putting out quality software are. Take Borland, for example. They put out a lot of good programs, and they're reaping the benefits. Microsoft too. Why they're complaining about BBSs is beyond me. Let's look at one BBS and see how Microsoft has benefited from it: the WPJ BBS. Besides carrying WPJ, there's a lot of other software (all legal) available from a CD-ROM that can be accessed by anybody that calls our BBS. Most of it is devoted to products produced by Microsoft (e.g., Windows), and we're just part of a much larger network of BBSs all over the world supporting Microsoft. True, a lot of commercial software companies live off Microsoft by writing apps helping people run Windows, but it goes both ways: Microsoft benefits by having so many companies support their products with add-on programs or whatever. If I was going to choose an operating system for my computer, one thing I would look at would be the third-party support, and Microsoft is the overwhelming leader in that area for IBM-compatible PCs. So, to hear Microsoft complaining about BBSs is more than a little annoying. Sounds like it's biting the hand that feeds it. - 64 -